package jmai.scanner;

import java.io.*;

import jmai.evaluator.Functions;

/**
 * @author - SDavis 4/2009
 * This class represents the Scanner.
 */
public class Scanner {

	/** enum for the parts of the line structure */
	public enum LinePart {
		PATTERN, LEFTBRACKET, ACTION, LEFTPAREN, PARAMETER1, COMMA, PARAMETER2, RIGHTPAREN, RIGHTBRACKET 
	}
	
	/** AST Tree */
	private ASTTree scannerTree;
	
	/** number of errors produced by program */
	private int syntaxErrors = 0;
	
	/** boolean for whether a pair node has been added to tree for the line */
	private boolean addedPair = false;
	
	/** counters for the number of left and right parenthesis in the pattern part */
	private int patternLeftParens = 0;
	private int patternRightParens = 0;
	
	/**
     * The constructor for the Scanner class.
     */	
	public Scanner() {
		
		/** instantiating the ASTTree object */
		scannerTree = new ASTTree();
		
	}
	
	/**
	 * @param inProgramFileName - name of program input file
	 * @return AST Tree - returns the AST tree built by the scanner
     * This reads in a program file line by line.
     */	
	public ASTTree ScanFile(String inProgramFileName) {

		try {
			
			syntaxErrors = 0;
			
			/** reading in each line from file and sending to processing module */
			File scannerFile = new File(inProgramFileName);
			FileReader scannerReader = new FileReader(scannerFile);
			BufferedReader scannerBufferedReader = new BufferedReader(scannerReader);
			String currentLine = new String();
			while ((currentLine = scannerBufferedReader.readLine()) != null) {
				ProcessProgramLine(currentLine);
			}
			scannerBufferedReader.close();
			
			ReportResults();

		} 
		catch (FileNotFoundException e) {
			System.out.println("jmai: program file not found");
			System.exit(0);
		} catch (IOException e) {
			System.out.println("jmai: program file error");
			System.exit(0);
	    } 

		return scannerTree;
		
	}
	
	/**
	 * @param currentLine - current line from program file
     * This module processes a line from the program file.
     */		
	public void ProcessProgramLine(String currentLine) {
	
		/** initializing to line part PATTERN */
		LinePart currentLinePart = LinePart.PATTERN;
		
		/** strings for current values of line tokens */
		String currentPattern = new String();
		String currentAction = new String();
		String currentParameter1 = new String();
		String currentParameter2 = new String();
		String currentRightParen = new String();
		String currentRightBracket = new String();
		
		/** initializing the strings for the current values of line tokens */
		currentPattern = "";
		currentAction = "";
		currentParameter1 = "";
		currentParameter2 = "";
		currentRightParen = "";
		currentRightBracket = "";
		
		/** number of pair being inserted into AST Tree by line */
		int pairNumber = 0;
		
		patternLeftParens = 0;
		patternRightParens = 0;
	
		addedPair = false;
		
		/** processing each character of the line one by one */
		for (int x = 0; x < currentLine.length(); x++) {

			/** processing for line part PATTERN */
			if (currentLinePart.equals(LinePart.PATTERN)) {
				if (currentLine.charAt(x) == '{') {
					if (patternLeftParens != patternRightParens) {
						ProcessSyntaxError(currentLine, x, "Invalid Pattern");
						return;
					}
					currentLinePart = LinePart.LEFTBRACKET;
					addedPair = true;
					currentPattern = currentPattern.trim();
					currentPattern = currentPattern.replaceAll(" ", "");
					if (currentPattern.equals("BEGIN")) {
						pairNumber = scannerTree.AddPairNode("Begin");
						scannerTree.AddRegEx(pairNumber, "");
					}
					else if (currentPattern.equals("END")) {
						pairNumber = scannerTree.AddPairNode("End");
						scannerTree.AddRegEx(pairNumber, "");
					}
					else if (currentPattern.equals("")) {
						pairNumber = scannerTree.AddPairNode("No RegEx");
						scannerTree.AddRegEx(pairNumber, "");
					}
					else {
						pairNumber = scannerTree.AddPairNode("RegEx");
						scannerTree.AddRegEx(pairNumber, currentPattern);
					}
				}
				else {
					if (currentLine.charAt(x) == '(') {
						patternLeftParens++;
					}
					else if (currentLine.charAt(x) == ')') {
						patternRightParens++;
					}
					currentPattern = currentPattern + currentLine.charAt(x);
					if (ValidatePattern(currentPattern) == false) {
						ProcessSyntaxError(currentLine, x, "Invalid Pattern");
						return;
					}
				}
			}
			/** processing for line part LEFTBRACKET */
			else if (currentLinePart.equals(LinePart.LEFTBRACKET)) {
		        currentLinePart = LinePart.ACTION;
		        currentAction = currentAction + currentLine.charAt(x);
		        if (ValidateAction(currentAction, currentPattern) == false) {
		        	ProcessSyntaxError(currentLine, x, "Invalid Action");
		        	return;
		        }
			}
			/** processing for line part ACTION */
			else if (currentLinePart.equals(LinePart.ACTION)) {
				if (currentLine.charAt(x) == '(') {
					currentLinePart = LinePart.LEFTPAREN;
				}
				else {
					currentAction = currentAction + currentLine.charAt(x);
					if (ValidateAction(currentAction, currentPattern) == false) {
						ProcessSyntaxError(currentLine, x, "Invalid Action");
						return;
					}
				}
			}
			/** processing for line part LEFTPAREN */
			else if (currentLinePart.equals(LinePart.LEFTPAREN)) {
		        currentLinePart = LinePart.PARAMETER1;
		        currentParameter1 = "";
		        currentParameter1 = currentParameter1 + currentLine.charAt(x);
		        if (ValidateParameter(currentAction, currentParameter1, 1) == false) {
		        	ProcessSyntaxError(currentLine, x, "Invalid Parameter");
		        	return;
		        }
			}
			/** processing for line part PARAMETER1 */
			else if (currentLinePart.equals(LinePart.PARAMETER1)) {
				if (currentLine.charAt(x) == ',') {
					currentLinePart = LinePart.COMMA;
					if (ValidateSecondParameter(currentAction) == false) {
						ProcessSyntaxError(currentLine, x, "Invalid Parameter");
						return;
					}
				}
				else if (currentLine.charAt(x) == ')') {
					if (ValidateSecondParameter(currentAction) == true) {
						ProcessSyntaxError(currentLine, x, "Invalid Parameter");
						return;	
					}
					currentLinePart = LinePart.RIGHTPAREN;
					currentRightParen = ")";
					currentAction = currentAction.trim();
					currentAction.trim();
					

					currentParameter1 = currentParameter1.replace("\"", "");
					currentParameter1 = currentParameter1.trim();
					scannerTree.AddAction(pairNumber, currentAction, currentParameter1, "");						
				}
				else {
					currentParameter1 = currentParameter1 + currentLine.charAt(x);
					if (ValidateParameter(currentAction, currentParameter1, 1) == false) {
						ProcessSyntaxError(currentLine, x, "Invalid Parameter");
						return;
					}
				}
			}	
			/** processing for line part COMMA */
			else if (currentLinePart.equals(LinePart.COMMA)) {
		        currentLinePart = LinePart.PARAMETER2;
		        currentParameter2 = "";
		        currentParameter2 = currentParameter2 + currentLine.charAt(x);
		        if (ValidateParameter(currentAction, currentParameter2, 2) == false) {
		        	ProcessSyntaxError(currentLine, x, "Invalid Parameter");
		        	return;
		        }
			}
			/** processing for line part PARAMETER2 */
			else if (currentLinePart.equals(LinePart.PARAMETER2)) {
				if (currentLine.charAt(x) == ')') {
					currentLinePart = LinePart.RIGHTPAREN;
					currentRightParen = ")";
					currentAction = currentAction.trim();
					currentAction.trim();

					currentParameter1 = currentParameter1.trim();
					currentParameter2 = currentParameter2.trim();
					scannerTree.AddAction(pairNumber, currentAction, currentParameter1, currentParameter2);						
				}
				else {
					currentParameter2 = currentParameter2 + currentLine.charAt(x);
					if (ValidateParameter(currentAction, currentParameter2, 2) == false) {
						ProcessSyntaxError(currentLine, x, "Invalid Parameter");
						return;
					}
				}
			}
			/** processing for line part RIGHTPAREN */
			else if (currentLinePart.equals(LinePart.RIGHTPAREN)) {
				if (currentLine.charAt(x) == ';') {
					currentLinePart = LinePart.ACTION;
					currentAction = "";
				}
				else if (currentLine.charAt(x) == '}') {
					currentLinePart = LinePart.RIGHTBRACKET;
					currentRightBracket = "}";
				}
				else {
					currentRightParen = currentRightParen + currentLine.charAt(x);
					if (ValidateEnding(currentRightParen) == false) {
			        	ProcessSyntaxError(currentLine, x, "Invalid Input");
			        	return;	
					}
				}
			}
			/** processing for line part RIGHTBRACKET */
			else if (currentLinePart.equals(LinePart.RIGHTBRACKET)) {
				currentRightBracket = currentRightBracket + currentLine.charAt(x);
				if (ValidateEnding(currentRightBracket) == false) {
		        	ProcessSyntaxError(currentLine, x, "Invalid Input");
		        	return;
				}
			}
			
		}
		
		/** handling lines where the final line part is not as expected */
		if (currentLinePart == LinePart.PATTERN) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Action");
			return;
		}
		else if (currentLinePart == LinePart.LEFTBRACKET) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Action");
			return;
		}		
		else if (currentLinePart == LinePart.ACTION) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Action");
			return;
		}		
		else if (currentLinePart == LinePart.LEFTPAREN) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Parameter");
			return;
		}		
		else if (currentLinePart == LinePart.PARAMETER1) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Parameter");
			return;
		}		
		else if (currentLinePart == LinePart.COMMA) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Parameter");
			return;
		}		
		else if (currentLinePart == LinePart.PARAMETER2) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Parameter");
			return;
		}		
		else if (currentLinePart == LinePart.RIGHTPAREN) {
			ProcessSyntaxError(currentLine, currentLine.length() - 1, "Invalid Action");
			return;
		}		

	}
	
	/**
	 * @param inString - string to test
	 * @return boolean - true if numeric and false is not numeric
     * This module decides whether a string is numeric.
     */		
	public boolean IsStringNumeric(String inString) {
		
		boolean isNumeric = true;
		
		for (int i = 0; i < inString.length(); i++) {
			
			if (inString.charAt(i) == '0') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '1') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '2') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '3') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '4') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '5') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '6') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '7') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '8') {
				isNumeric = true;
			}
			else if (inString.charAt(i) == '9') {
				isNumeric = true;
			}
			else {
				isNumeric = false;
			}
		
			if (isNumeric == false) {
				return false;
			}
			
		}
		
		return true;
	
	}
	
	/**
	 * @param inPattern - current pattern to validate
	 * @return boolean true if valid and false if not valid
     * This module validates a pattern token.
     */		
	public boolean ValidatePattern(String inPattern) {
		
		inPattern = inPattern.trim();

		if (inPattern.equals("")) {
			return true;
		}
		else if (inPattern.length() < 6 && inPattern.equals("BEGIN".substring(0, inPattern.length()))) {
			return true;
		}
		else if (inPattern.length() < 4 && inPattern.equals("END".substring(0, inPattern.length()))) {
			return true;
		}
		else if (ValidateRegEx(inPattern)) {
			return true;
		}
		else {
			return false;
		}
		
	}
	
	/**
	 * @param inRegEx - current regular expression to validate
	 * @return boolean true if valid and false if not valid
     * This module validates a regular expression token.
     */		
	public boolean ValidateRegEx(String inRegEx) {
		
		inRegEx = inRegEx.trim();
		inRegEx = inRegEx.replaceAll(" ", "");
		
		for (int i = 0; i < inRegEx.length(); i++) {
			if (inRegEx.charAt(i) != 'a' && inRegEx.charAt(i) != 'b' && inRegEx.charAt(i) != 'c' && inRegEx.charAt(i) != '+' && inRegEx.charAt(i) != '*' && inRegEx.charAt(i) != '(' && inRegEx.charAt(i) != ')' && inRegEx.charAt(i) != '|' && inRegEx.charAt(i) != '?') {
				return false;
			}
		}
		
		if (inRegEx.charAt(0) == '+' || inRegEx.charAt(0) == '*' || inRegEx.charAt(0) == '|' || inRegEx.charAt(0) == '?') {
			return false;
		}
		
		if (inRegEx.contains("(+") || inRegEx.contains("(*") || inRegEx.contains("(|") || inRegEx.contains("|)") || inRegEx.contains("(?")) {
			return false;
		}
		
		if (inRegEx.contains("++") || inRegEx.contains("+*") || inRegEx.contains("+?")) {
			return false;
		}
		
		if (inRegEx.contains("*+") || inRegEx.contains("**") || inRegEx.contains("*?")) {
			return false;
		}
		
		if (inRegEx.contains("?+") || inRegEx.contains("?*") || inRegEx.contains("??")) {
			return false;
		}		
		
		if (inRegEx.contains("|+") || inRegEx.contains("|*") || inRegEx.contains("|?") || inRegEx.contains("||")) {
			return false;
		}		
		
		if (patternRightParens > patternLeftParens) {
			return false;
		}
		
		return true;
		
	}
	
	/**
	 * @param inAction - current action to validate
	 * @return boolean true if valid and false if not valid
     * This module validates an action token.
     */		
	public boolean ValidateAction(String inAction, String inPattern) {
		
		inAction = inAction.trim();
		
		if (inAction.equals("")) {
			return true;
		}
		else if (inAction.length() < 6 && inAction.equals("print".substring(0, inAction.length()))) {
			return true;
		}
		else if (inAction.length() < 10 && inAction.equals("substring".substring(0, inAction.length()))) {
			if (inPattern.equals("BEGIN") || inPattern.equals("END")) {
				return false;
			}
			else {
				return true;
			}
		}
		else if (inAction.length() < 8 && inAction.equals("replace".substring(0, inAction.length()))) {
			if (inPattern.equals("BEGIN") || inPattern.equals("END")) {
				return false;
			}
			else {
				return true;
			}
		}
		else if (inAction.length() < 7 && inAction.equals("insert".substring(0, inAction.length()))) {
			if (inPattern.equals("BEGIN") || inPattern.equals("END")) {
				return false;
			}
			else {
				return true;
			}
		}
		else if (inAction.length() < 7 && inAction.equals("remove".substring(0, inAction.length()))) {
			if (inPattern.equals("BEGIN") || inPattern.equals("END")) {
				return false;
			}
			else {
				return true;
			}
		}
		else {
			return false;
		}
		
	}
	
	/**
	 * @param inAction - current action to validate for second parameter
	 * @return boolean true if valid and false if not valid
     * This module validates whether a second parameter is needed for an action.
     */		
	public boolean ValidateSecondParameter(String inAction) {
		
		inAction = inAction.trim();
		
		if (inAction.equals("print")) {
			return false;
		}
		else if (inAction.equals("substring")) {
			return true;
		}
		else if (inAction.equals("replace")) {
			return true;
		}
		else if (inAction.equals("insert")) {
			return true;
		}
		else if (inAction.equals("remove")) {
			return false;
		}
		else {
			return false;
		}
		
	}
	
	/**
	 * @param inAction - current action to validate parameter for
	 * @param inParameter - current parameter to validate
	 * @param parameterPosition - either 1 for first parameter or 2 for second parameter
	 * @return boolean true if valid and false if not valid
     * This module validates a parameter token.
     */	
	public boolean ValidateParameter(String inAction, String inParameter, int parameterPosition) {

		inAction = inAction.trim();
		inParameter = inParameter.trim();

		if (inParameter.equals("")) {
			return true;
		}
		else if (inAction.equals("print")) {
			if (inParameter.length() < 5 && inParameter.equals("LINE".substring(0, inParameter.length()))) {
				return true;
			}
			else if (inParameter.charAt(0) == '\"') {
				return true;
			}
			else {
				return false;
			}
		}
		else if (inAction.equals("substring")) {
			if (inParameter.length() < 4 && inParameter.equals("EOL".substring(0, inParameter.length()))) {
				return true;
			}
			else if (IsStringNumeric(inParameter)) {
				return true;
			}
			else {
				return false;
			}
		}
		else if (inAction.equals("replace")) {
			if (inParameter.equals("a") || inParameter.equals("b") || inParameter.equals("c")) {
				return true;
			}
			else {
				return false;
			}
		}
		else if (inAction.equals("insert")) {
			if (parameterPosition == 1) {
				if (inParameter.length() < 4 && inParameter.equals("EOL".substring(0, inParameter.length()))) {
					return true;
				}
				else if (IsStringNumeric(inParameter)) {
					return true;
				}
				else {
					return false;
				}
			}
			else {
				if (inParameter.equals("a") || inParameter.equals("b") || inParameter.equals("c")) {
					return true;
				}
				else {
					return false;
				}
			}
		}
		else if (inAction.equals("remove")) {
			if (inParameter.equals("a") || inParameter.equals("b") || inParameter.equals("c")) {
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return false;
		}
		
	}

	/**
	 * @param inEnding - current ending to validate
	 * @return boolean true if valid and false if not valid
     * This module validates whether an ending is valid (i.e., either ')' or '}').
     */		
	public boolean ValidateEnding(String inEnding) {
		
		inEnding = inEnding.trim();
		
		if (inEnding.equals(")") || inEnding.equals("}")) {
			return true;
		}
		else {
			return false;
		}
		
	}

	/**
	 * @param inLine - current line
	 * @param inPosition - character being accessed when error occurred
	 * @param errorDescription - description of error
     * This module handles a syntax error by printing details to console.
     */		
	public void ProcessSyntaxError(String inLine, int inPosition, String errorDescription) {

		int numberPairs = 0;
		
		syntaxErrors++;
		
		System.out.println("Syntax Error " + syntaxErrors + " at character:  '" + inLine.charAt(inPosition) + "' (position:  " + (inPosition + 1) + ")");
		System.out.println("Syntax Error " + syntaxErrors + " line:  " + inLine);
		System.out.println("Syntax Error " + syntaxErrors + " description:  " + errorDescription);
		System.out.println("Syntax Error " + syntaxErrors + " trace:");

		numberPairs = scannerTree.GetNumberOfPairs();
		if (addedPair == true) {
	    	System.out.println("-----<Program Root>");
			scannerTree.OutputPair(numberPairs);
		}
		else {
	    	System.out.println("-----<Program Root>");
	    	System.out.println("----------<Pair 1>");
		}
		
		/** removing the pair being added when error occurred */
		if (addedPair == true) {
			scannerTree.RemoveLastPairNode();
		}
		
	}
	
	/**
     * This module prints to console the number of syntax errors.
     */		
	public void ReportResults() {
		
		System.out.println("The program scanned with " + syntaxErrors + " syntax error(s).");
		
	}
	
	/**
	 * @return AST Tree for the scanner
     * This module gets the AST Tree for the scanner.
     */		
	public ASTTree GetASTTree() {

		return scannerTree;

	}
	
}
